package top.dotgo.kit;

import cn.hutool.cache.Cache;
import cn.hutool.cache.CacheUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.StrUtil;
import top.dotgo.kit.bean.AccInfoBean;
import top.dotgo.kit.cache.RedisKit;
import top.dotgo.kit.ex.DotException;
import top.dotgo.kit.ex.DotMsg;

import java.util.HashMap;
import java.util.Map;

/**
 * @author : lin
 * @date : 2019/10/10 14:22
 */
public class ContextKit {

    /**
     * SESSION失效时间，12小时内登录后，自动续期，超过12小时 需要更新token
     */
    private static final int SESSION_TIME = 60 * 60 * 12;
    /**
     * 内存缓存对象 失效时间 1小时
     */
    private static final int CACHE_TIME = 1000 * 60 * 60;
    /**
     * 内存缓存对象 初始大小
     */
    private static final int CACHE_SIZE = 20;

    private static final String SERVLET_REQUEST_KEY = "SERVLET_REQUEST_KEY";
    private static final String SERVLET_RESPONSE_KEY = "SERVLET_RESPONSE_KEY";
    private static final String USER_CACHE_KEY = "USER_CACHE_KEY";
    private static final String ACC_ID_KEY = "ACC_ID_KEY";
    private static final String ACC_INFO_KEY = "ACC_INFO_KEY";
    private static final String FROM_KEY = "FROM_KEY";
    private static final String TOKEN_KEY = "TOKEN_KEY";
    private static final ThreadLocal<HashMap<String, Object>> CONTEXT = new ThreadLocal<>();

    private static final Cache<String, Map<String, Object>> CACHE = CacheUtil.newLFUCache(CACHE_SIZE);

    private ContextKit() {
    }

    /**
     * 获取 Request
     *
     * @return Request
     */
    public static Object getRequest() {
        return get(SERVLET_REQUEST_KEY, Object.class);
    }

    /**
     * 缓存 Request
     * <p>
     * 程变量：true
     *
     * @param value value
     */
    public static void setRequest(Object value) {
        put(SERVLET_REQUEST_KEY, value, false, false);
    }

    /**
     * 获取 Response
     *
     * @return Response
     */
    public static Object getResponse() {
        return get(SERVLET_RESPONSE_KEY, Object.class);
    }

    /**
     * 缓存 Response
     * <p>
     * 线程变量：true
     *
     * @param value Response
     */
    public static void setResponse(Object value) {
        put(SERVLET_RESPONSE_KEY, value, false, false);
    }

    /**
     * 获取 请求来源
     * 默认值 BIZ
     *
     * @return {@link Const.from}
     */
    public static Const.from getFrom() {
        String from = get(FROM_KEY, String.class);
        if (from == null) {
            return Const.from.BIZ;
        }
        return Const.from.valueOf(from);
    }

    /**
     * 缓存 请求来源
     * <p>
     * 线程变量：true
     *
     * @param from from
     */
    public static void setFrom(Const.from from) {
        put(FROM_KEY, from.name(), false, false);
    }

    /**
     * 获取 账号 扩展信息
     *
     * @param key key
     * @return Object
     */
    public static Object getAccExt(String key) {
        AccInfoBean bean = getAccInfo();
        if (bean == null) {
            return null;
        }
        if (!bean.getExt().containsKey(key)) {
            return null;
        }
        return bean.getExt().get(key);
    }

    /**
     * 缓存 账号 扩展信息
     * <p>
     * 线程变量：true，cache：true，redis：true
     *
     * @param key   key
     * @param value value
     */
    public static void setAccInfoExt(String key, Object value) {
        AccInfoBean bean = getAccInfo();
        if (bean == null) {
            return;
        }
        bean.getExt().put(key, value);
        setAccInfo(bean);
    }

    /**
     * 获取账号信息
     *
     * @return AccInfoBean
     */
    public static AccInfoBean getAccInfo() {
        return get(ACC_INFO_KEY, AccInfoBean.class);
    }

    /**
     * 缓存 账号信息
     * <p>
     * 线程变量：true，cache：true，redis：true
     *
     * @param bean bean
     */
    public static void setAccInfo(AccInfoBean bean) {
        put(ACC_INFO_KEY, bean, true, true);
    }

    /**
     * 获取账号id
     *
     * @return accId
     */
    public static Object getAccId() {
        Map<String, Object> container = CONTEXT.get();
        if (container == null || !container.containsKey(ACC_ID_KEY)) {
            throw new DotException(DotMsg.LOGIN_ERR, DotMsg.LOGIN_ERR_MSG);
        }
        return container.get(ACC_ID_KEY);
    }

    /**
     * 缓存 用户账号id
     * <p>
     * 线程变量：true
     *
     * @param value value
     */
    public static void setAccId(Object value) {
        put(ACC_ID_KEY, value, false, false);
    }

    /**
     * 获取 token
     *
     * @return token
     */
    public static String getToken() {
        return get(TOKEN_KEY, String.class);
    }

    /**
     * 缓存token
     * <p>
     * 线程变量：true，cache缓存：true
     *
     * @param token token
     */
    public static void setToken(String token) {
        put(TOKEN_KEY, token, true, false);
    }

    /**
     * 线程中 放变量
     *
     * @param key   key
     * @param value value
     */
    private static void put(String key, Object value, boolean isCache, boolean isRedis) {
        //默认 存入 缓存变量
        HashMap<String, Object> container = CONTEXT.get();
        if (container == null) {
            container = new HashMap<>(20);
            CONTEXT.set(container);
        }
        CONTEXT.get().put(key, value);

        //如果存入本地cache
        if (isCache) {
            String accId = String.valueOf(getAccId());
            if (!CACHE.containsKey(accId)) {
                CACHE.put(accId, new HashMap<>(), CACHE_TIME);
            }
            CACHE.get(accId).put(key, value);
        }
        //如果存入 redis
        if (isRedis) {
            String accId = String.valueOf(getAccId());
            String cacheKey = getCacheKey(accId);
            RedisKit.hset(cacheKey, key, value);
            RedisKit.expire(cacheKey, SESSION_TIME);
        }

    }

    /**
     * 线程中 获取变量
     *
     * @param key key
     * @return Object
     */
    public static <T> T get(String key, Class<T> clazz) {
        Map<String, Object> container = CONTEXT.get();
        T value = null;
        //先取 线程变量
        if (container != null) {
            value = Convert.convert(clazz, container.get(key));
        }


        //再取 cache
        if (value == null) {
            Object accId = getAccId();
            container = CACHE.get(String.valueOf(accId));
            if (container != null) {
                value = Convert.convert(clazz, container.get(key));
            }
        }

        //再取redis
        if (value == null) {
            Object accId = getAccId();
            String cacheKey = getCacheKey(accId);
            value = RedisKit.hget(cacheKey, key, clazz);
            RedisKit.expire(cacheKey, SESSION_TIME);
        }

        return value;
    }

    /**
     * 退出登录
     */
    public static void logout() {
        clear(getAccId());
    }

    /**
     * 清除 指定变量
     *
     * @param key key
     */
    public static void remove(String key) {
        //先清除 线程变量
        Map<String, Object> container = CONTEXT.get();
        if (container != null) {
            container.remove(key);
        }
        Object accId = getAccId();
        //再清除 cache 变量
        container = CACHE.get(String.valueOf(accId));
        if (container != null) {
            container.remove(key);
        }
        //再清除 redis 变量
        String cacheKey = getCacheKey(accId);
        RedisKit.hset(cacheKey, key, null);
    }

    /**
     * 清理 线程变量
     */
    public static void clear() {
        CONTEXT.remove();
    }

    /**
     * 清理 指定账号 缓存数据
     * <p>
     * 线程变量 及 cache变量
     *
     * @param accId accId
     */
    public static void clear(Object accId) {
        //先清理 ThreadLocal
        CONTEXT.remove();
        //清理 Cache
        CACHE.remove(String.valueOf(accId));
    }


    /**
     * getCacheKey
     *
     * @param accId accId
     * @return String
     */
    private static String getCacheKey(Object accId) {
        return StrUtil.format("{}:{}", USER_CACHE_KEY, accId);
    }
}
